#include "Simulator.hpp"
#include <stdlib.h>
#include <cmath>
#include <stdio.h>
#include <iostream>
#include <iomanip>
#include "VTKPathWriter.hpp"

Simulator::Simulator(const std::string& filename)
{
    reader=FileReader();
    //redistering keys
    reader.RegisterIntParameter(p_PART_OUT_FREQ,v_PART_OUT_FREQ);
    reader.RegisterIntParameter(p_VTK_OUT_FREQ,v_VTK_OUT_FREQ);
    reader.RegisterIntParameter(p_CL_WORKGROUP_SIZE,v_CL_WORKGROUP_SIZE);
    reader.RegisterIntParameter(p_CLWORKGROUP_1DSIZE,v_CLWORKGROUP_1DSIZE);
    reader.RegisterIntParameter(p_CLWORKGROUP_3DSIZE_X,v_CLWORKGROUP_3DSIZE_X);
    reader.RegisterIntParameter(p_CLWORKGROUP_3DSIZE_Y,v_CLWORKGROUP_3DSIZE_Y);
    reader.RegisterIntParameter(p_CLWORKGROUP_3DSIZE_Z,v_CLWORKGROUP_3DSIZE_Z);
    reader.RegisterIntParameter(p_XN,v_XN);
    reader.RegisterIntParameter(p_YN,v_YN);
    reader.RegisterIntParameter(p_ZN,v_ZN);

    reader.RegisterIntParameter(p_REFLECTX,v_REFLECTX);
    reader.RegisterIntParameter(p_REFLECTY,v_REFLECTY);
    reader.RegisterIntParameter(p_REFLECTZ,v_REFLECTZ);


    reader.RegisterDoubleParameter(p_TIME_STEP,v_TIME_STEP);
    reader.RegisterDoubleParameter(p_END_TIME,v_END_TIME);
    reader.RegisterDoubleParameter(p_EPSILON,v_EPSILON);
    reader.RegisterDoubleParameter(p_SIGMA,v_SIGMA);
    reader.RegisterDoubleParameter(p_X_MIN,v_X_MIN);
    reader.RegisterDoubleParameter(p_X_MAX,v_X_MAX);
    reader.RegisterDoubleParameter(p_Y_MIN,v_Y_MIN);
    reader.RegisterDoubleParameter(p_Y_MAX,v_Y_MAX);
    reader.RegisterDoubleParameter(p_Z_MIN,v_Z_MIN);
    reader.RegisterDoubleParameter(p_Z_MAX,v_Z_MAX);
    reader.RegisterDoubleParameter(p_R_CUT,v_R_CUT);
    reader.RegisterDoubleParameter(p_RHO_0,v_RHO_0);
    reader.RegisterDoubleParameter(p_K_GAS,v_K_GAS);
    reader.RegisterDoubleParameter(p_VISCO,v_VISCO);

    reader.RegisterDoubleParameter(p_GX,v_GX);
    reader.RegisterDoubleParameter(p_GY,v_GY);
    reader.RegisterDoubleParameter(p_GZ,v_GZ);


    reader.RegisterStringParameter(p_INPUT_FILE,v_INPUT_FILE);
    reader.RegisterStringParameter(p_PART_OUT_NAME,v_PART_OUT_NAME);
    reader.RegisterStringParameter(p_VTK_OUT_NAME,v_VTK_OUT_NAME);

    //Parsing parameters
    reader.ReadFile(filename);
    WORK_GROUP_SIZE=reader.GetIntParameter(p_CL_WORKGROUP_SIZE);
    delta_t=reader.GetDoubleParameter(p_TIME_STEP);
    end_t=reader.GetDoubleParameter(p_END_TIME);
    vtkFreq = reader.GetIntParameter(p_VTK_OUT_FREQ);
    partfreq = reader.GetIntParameter(p_PART_OUT_FREQ);
    nx =  reader.GetIntParameter(p_XN);
    ny =  reader.GetIntParameter(p_YN);
    nz =  reader.GetIntParameter(p_ZN);
    x_min =reader.GetDoubleParameter(p_X_MIN);
    x_max =reader.GetDoubleParameter(p_X_MAX);
    y_min =reader.GetDoubleParameter(p_Y_MIN);
    y_max =reader.GetDoubleParameter(p_Y_MAX);
    z_min =reader.GetDoubleParameter(p_Z_MIN);
    z_max =reader.GetDoubleParameter(p_Z_MAX);
    len_x= (x_max-x_min)/nx;
    len_y=  (y_max-y_min)/ny;
    len_z=  (z_max-z_min)/nz;
    cut_r= reader.GetDoubleParameter(p_R_CUT);
    part_out_name= reader.GetStringParameter(p_PART_OUT_NAME);
    vtk_out_name= reader.GetStringParameter(p_VTK_OUT_NAME);
    gravity.s[0]=reader.GetDoubleParameter(p_GX);
    gravity.s[1]=reader.GetDoubleParameter(p_GY);
    gravity.s[2]=reader.GetDoubleParameter(p_GZ);
    gravity.s[3]=0.0;
    reflectx=reader.GetIntParameter(p_REFLECTX);
    reflecty=reader.GetIntParameter(p_REFLECTY);
    reflectz=reader.GetIntParameter(p_REFLECTZ);
    viscosity=reader.GetDoubleParameter(p_VISCO);
    //Simulation containers
    NCells=nx*ny*nz;
    cells=new cl_int[NCells];
    std::string fileParti=reader.GetStringParameter(p_INPUT_FILE);

    //Reads particle file
    if(!reader.readParticleFile(NParticles,masses,positions,velocities,fileParti))
        exit(1);

    particle_ids = new cl_int[NParticles];
    densities = new cl_real[NParticles];
    pressures = new cl_real[NParticles];

    //Buffers stuff
    GLOBAL_CELLS_SIZE=ceil(NCells/(float)WORK_GROUP_SIZE)*WORK_GROUP_SIZE;
    GLOBAL_PARTICLES_SIZE=ceil(NParticles/(float)WORK_GROUP_SIZE)*WORK_GROUP_SIZE;


}

void Simulator::MemoryBuffersAllocation()
{
    try
    {
        posBuffer = cl::Buffer(context, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR ,sizeof(cl_real4) * NParticles, &positions[0]);
        //Velocities Buffer
        std::cout<<"1\n";
        velBuffer = cl::Buffer(context, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR ,sizeof(cl_real4) * NParticles, &velocities[0]);
        std::cout<<"1\n";
        cellsBuffer = cl::Buffer(context, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR ,sizeof(cl_int) * NCells, &cells[0]);
        std::cout<<"2\n";
        part_id_Buffer = cl::Buffer(context, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR ,sizeof(cl_int) * NParticles, &particle_ids[0]);
        std::cout<<"3\n";
        massesBuffer = cl::Buffer(context, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR ,sizeof(cl_real) * NParticles, &masses[0]);
        std::cout<<"4\n";
        densityBuffer = cl::Buffer(context, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR ,sizeof(cl_real) * NParticles, &densities[0]);
        std::cout<<"5\n";
        invDenBuffer = cl::Buffer(context, CL_MEM_ALLOC_HOST_PTR ,sizeof(cl_real) * NParticles);
        std::cout<<"6\n";
        pressureBuffer = cl::Buffer(context, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR ,sizeof(cl_real) * NParticles, &pressures[0]);
        std::cout<<"7\n";
        accelBuffer = cl::Buffer(context, CL_MEM_ALLOC_HOST_PTR, sizeof(cl_real4) * NParticles);
    }
    catch(cl::Error er)
    {
        printf("ERROR: %s(%d)\n", er.what(), er.err());
    }
}

void Simulator::runWithGraphics(const cl_uint& repeat)
{
    try
    {
        for(cl_uint i=0;i<repeat;++i)
        {
            queue.enqueueNDRangeKernel(boundariesK, cl::NullRange, cl::NDRange(GLOBAL_PARTICLES_SIZE), cl::NDRange(WORK_GROUP_SIZE));
            queue.enqueueNDRangeKernel(emptyCellsK, cl::NullRange, cl::NDRange(GLOBAL_CELLS_SIZE), cl::NDRange(WORK_GROUP_SIZE));
            queue.enqueueNDRangeKernel(fillCellsK, cl::NullRange, cl::NDRange(GLOBAL_CELLS_SIZE), cl::NDRange(WORK_GROUP_SIZE));
            queue.enqueueNDRangeKernel(densityK, cl::NullRange, cl::NDRange(GLOBAL_PARTICLES_SIZE), cl::NDRange(WORK_GROUP_SIZE));
            queue.enqueueNDRangeKernel(accelerationK, cl::NullRange, cl::NDRange(GLOBAL_PARTICLES_SIZE), cl::NDRange(WORK_GROUP_SIZE));
            queue.enqueueNDRangeKernel(updatingPVK, cl::NullRange, cl::NDRange(GLOBAL_PARTICLES_SIZE), cl::NDRange(WORK_GROUP_SIZE));
        }
        queue.enqueueReadBuffer(posBuffer, CL_TRUE, 0, sizeof(cl_real4) * NParticles, positions);
        queue.enqueueReadBuffer(velBuffer, CL_TRUE, 0, sizeof(cl_real4) * NParticles, velocities);
        queue.enqueueReadBuffer(densityBuffer, CL_TRUE, 0, sizeof(cl_real) * NParticles, densities);
        queue.enqueueReadBuffer(pressureBuffer, CL_TRUE, 0, sizeof(cl_real) * NParticles, pressures);
    }
    catch(cl::Error er)
    {
        printf("ERROR: %s(%d)\n", er.what(), er.err());
    }
}

void Simulator::runWithVTK()
{
    try
    {
        int counter=0,n_print=0,p_print=0;
        for(cl_real t=0;t<end_t;t+=delta_t)
        {
            if(counter%partfreq==0)
            {
                queue.enqueueReadBuffer(posBuffer, CL_TRUE, 0, sizeof(cl_real4) * NParticles, positions);
                queue.enqueueReadBuffer(velBuffer, CL_TRUE, 0, sizeof(cl_real4) * NParticles, velocities);
                queue.enqueueReadBuffer(densityBuffer, CL_TRUE, 0, sizeof(cl_real) * NParticles, densities);
                queue.enqueueReadBuffer(pressureBuffer, CL_TRUE, 0, sizeof(cl_real) * NParticles, pressures);
                //std::string filename="vtkFiles//";
                std::string filename=part_out_name;
                filename+=std::to_string(p_print);
                filename += ".out";
                std::ofstream mStream;
                mStream.open(filename);
                mStream << NParticles<<"\n";

                for(cl_uint i(0);i<NParticles;++i){
                    mStream<<std::fixed;
                    mStream<< std::setprecision(6) << masses[i] << "  "<< positions[i].s[0]<<"  "<<positions[i].s[1]<<"  "<<positions[i].s[2]<<"\t";
                    mStream<<velocities[i].s[0]<<"  "<<velocities[i].s[1]<<"  "<<velocities[i].s[2]<<"\n";
                }
                ++p_print;
                mStream.close();
            }
            if(counter%vtkFreq==0)
            {
                queue.enqueueReadBuffer(posBuffer, CL_TRUE, 0, sizeof(cl_real4) * NParticles, positions);
                queue.enqueueReadBuffer(velBuffer, CL_TRUE, 0, sizeof(cl_real4) * NParticles, velocities);
                queue.enqueueReadBuffer(densityBuffer, CL_TRUE, 0, sizeof(cl_real) * NParticles, densities);
                queue.enqueueReadBuffer(pressureBuffer, CL_TRUE, 0, sizeof(cl_real) * NParticles, pressures);
                //std::string filename1="vtkFiles//";
                std::string filename1=vtk_out_name;
                filename1+=std::to_string(n_print);
                VTKPathWriter writer(filename1,NParticles);
                writer.writePoints(positions);
                writer.writeScalars(masses,"m");
                writer.writeScalars(densities,"rho");
                writer.writeScalars(pressures,"p");
                writer.writeVector(velocities,"v");
                ++n_print;
            }
            ++counter;
            queue.enqueueNDRangeKernel(boundariesK, cl::NullRange, cl::NDRange(GLOBAL_PARTICLES_SIZE), cl::NDRange(WORK_GROUP_SIZE));
            queue.enqueueNDRangeKernel(emptyCellsK, cl::NullRange, cl::NDRange(GLOBAL_CELLS_SIZE), cl::NDRange(WORK_GROUP_SIZE));
            queue.enqueueNDRangeKernel(fillCellsK, cl::NullRange, cl::NDRange(GLOBAL_CELLS_SIZE), cl::NDRange(WORK_GROUP_SIZE));
            queue.enqueueNDRangeKernel(densityK, cl::NullRange, cl::NDRange(GLOBAL_PARTICLES_SIZE), cl::NDRange(WORK_GROUP_SIZE));
            queue.enqueueNDRangeKernel(accelerationK, cl::NullRange, cl::NDRange(GLOBAL_PARTICLES_SIZE), cl::NDRange(WORK_GROUP_SIZE));
            queue.enqueueNDRangeKernel(updatingPVK, cl::NullRange, cl::NDRange(GLOBAL_PARTICLES_SIZE), cl::NDRange(WORK_GROUP_SIZE));
        }
        //queue.finish();
    }
    catch(cl::Error er)
    {
        printf("ERROR: %s(%d)\n", er.what(), er.err());
    }
}
